package org.budo.support.spring.data.redis.connection.jedis;

import org.budo.support.java.net.util.JavaNetUtil;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConnection;
import org.springframework.data.redis.connection.jedis.JedisConverters;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;

/**
 * @author lmw
 * @see org.springframework.data.redis.connection.RedisConnectionFactory
 * @see org.springframework.data.redis.connection.jedis.JedisConnectionFactory
 */
@Slf4j
@Getter
@Setter
public class BudoJedisConnectionFactory implements RedisConnectionFactory, InitializingBean {
    private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(JedisConverters.exceptionConverter());

    private String hostName = "localhost";

    private Integer port = Protocol.DEFAULT_PORT;

    private Integer database = 0;

    private String password;

    private Integer timeout = Protocol.DEFAULT_TIMEOUT;

    private boolean convertPipelineAndTxResults = true;

    private JedisPoolConfig poolConfig = new JedisPoolConfig();

    private JedisPool _jedisPool;

    @Override
    public RedisConnection getConnection() {
        Jedis jedis = this.jedisPool().getResource();

        return new BudoJedisConnection(jedis, this);
    }

    @Override
    public RedisClusterConnection getClusterConnection() {
        throw new RuntimeException();
    }

    @Override
    public RedisSentinelConnection getSentinelConnection() {
        throw new RuntimeException();
    }

    @Override
    public boolean getConvertPipelineAndTxResults() {
        return this.convertPipelineAndTxResults;
    }

    @Override
    public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
        log.error("#75 translateExceptionIfPossible, this=" + this, ex);

        return EXCEPTION_TRANSLATION.translate(ex);
    }

    private JedisPool jedisPool() {
        if (null != this._jedisPool) {
            return this._jedisPool;
        }

        synchronized (BudoJedisConnectionFactory.class) {
            if (null != this._jedisPool) {
                return this._jedisPool;
            }

            if (!JavaNetUtil.isPortUsing(this.getHostName(), this.getPort(), this.getTimeout()) //  
                    || !JavaNetUtil.reachable(this.getHostName(), this.getPort())) {
                throw new IllegalStateException("#67 redis not ok, host=" + this.getHostName() + ", port=" + this.getPort());
            }

            return this._jedisPool = new JedisPool(this.getPoolConfig(), // 
                    this.getHostName(), //
                    this.getPort(), //
                    this.getTimeout(), //
                    this.getPassword(), // 
                    this.getDatabase());
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    }

    @Override
    public String toString() {
        return super.toString() //  
                + ", hostName=" + this.getHostName() // 
                + ", port=" + this.getPort() // 
                + ", password=" + this.getPassword() // 
                + ", database=" + this.getDatabase() // 
                + ", timeout=" + this.getTimeout();
    }
}